package de.jlippert;
/* 
 * Copyright (C) 2013, Jan Lippert (j.li1202yt@web.de)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;

/**
 * Created by Jan Lippert (j.li1202yt@web.de)
 * Date: 15.05.2013
 * Time: 20:05:00
 */

/**
 *
 * This class creates a 5-Star rating-component, like Amazon.com uses on their side.
 * 
 * JRating is able to display 11 differend states (0 - 5 stars with a distance of 1/2 stars).
 * The value of JRating can be set via using the method "setRating()".
 * You can fix the Value by using the method "setFixed()".
 * If the rating is not fixed, you can hold down the left mousebutton and scroll it to the side to 
 * change the rating-value. By calling the method "getRating()", you can get the value of the rating.
 * If you want to show the rating as a string, you can use the method "setRatingLabel()" and give an 
 * instance of an JLabel-Object, which will display the rating as a String (0.0, 0.5, 1.0, etc.).
 * You can also set all these options via using different constructors.
 * 
 *
 * -------------------------------------
 * A simple testformular for JRating:
 * -------------------------------------
 *
   import javax.swing.*;

   public class JRatingDemo {
         public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                 public void run() {
                     JFrame frame = new JFrame("JRating Demo");
                     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                     JRating jRating = new JRating(2.5, false);
                     JLabel ratingLabel = new JLabel();
                     jRating.setRatingLabel(ratingLabel);
                     frame.getContentPane().add(jRating);
                     frame.getContentPane().add(ratingLabel);
                     frame.setSize(250, 80);
                     frame.setVisible(true);
                }
             });
         }
   }
 *
 */

public class JRating extends JComponent implements MouseMotionListener {

    private static final long serialVersionUID = 3804853854710428403L;
    private static final int JR_WIDTH = 65;
    private static final int JR_HEIGHT = 12;
    private static final Dimension JR_DIMENSION = new Dimension(JR_WIDTH, JR_HEIGHT);
    private static final BufferedImage[] RATING_SPRITES = createRatingSprites();
    private static BufferedImage[] createRatingSprites() {
        try {
            /*
               The basic-image is an PNG-Image with transparency(Alphachannel). It contains 4 images per line at 3 lines (the 12. image is empty!).
               Instead of reading the image from the harddisk, we are creating the image via ImageIO from this byte-array:
             */
            BufferedImage baseImage = ImageIO.read(
                new ByteArrayInputStream(new byte[] {
                  -119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, -61, 0, 0, 0, 48, 8, 6, 0, 0, 0, -84, 121,
                  -50, 29, 0, 0, 0, 25, 116, 69, 88, 116, 83, 111, 102, 116, 119, 97, 114, 101, 0, 65, 100, 111, 98, 101, 32, 73, 109, 97, 103, 101,
                  82, 101, 97, 100, 121, 113, -55, 101, 60, 0, 0, 6, 42, 73, 68, 65, 84, 120, -38, -20, -100, -17, 79, 91, 85, 24, -57, -97, 123, 109,
                  11, -123, -2, 88, 91, -74, -15, 107, 81, -120, 99, 72, 6, -60, -124, 55, 50, 117, -119, 123, -125, -53, 38, -55, 124, 97, 112, -2, 1, 100,
                  -39, 75, 93, -30, 11, 99, 124, 105, -116, -68, -14, 63, 16, -100, -66, -16, 5, -109, -52, -59, 31, 51, -117, 99, -58, 68, -103, 115, 27, 19,
                  52, 12, 104, 71, -117, -4, 40, 109, 113, 101, 101, 112, -19, 115, -96, 77, 89, 75, -42, -34, -34, 30, -50, 125, 78, 79, -46, 28, -50, 57,
                  -9, 27, -62, 39, -49, 73, -49, -67, -9, 19, 64, -45, 52, -56, -11, 25, -4, 124, 80, -37, 109, 77, -106, -52, 87, 31, -100, 41, 51, -112,
                  -120, -127, 10, 57, 90, -64, -17, -41, 98, -85, 49, -104, -102, -70, -81, 65, -98, -115, 90, 102, -30, -57, -117, -102, 125, -27, 110, -103, 65, -110,
                  1, -10, 50, 48, -56, -71, 25, -18, -36, -71, 13, 78, -121, 19, 38, 39, -18, -27, -5, 123, -56, 101, -42, 102, -82, -126, -73, -50, 87, 102,
                  -112, 100, -128, -67, 12, 12, -78, 54, -61, 124, 40, -92, 77, -49, -52, -62, -39, 119, -50, 42, -40, -29, -8, 105, -65, -124, 90, 102, 105, -14,
                  -90, -90, -60, -1, 6, -89, -53, -70, 50, 51, -21, -105, -104, -63, 100, -110, -127, 5, 86, -25, -57, -39, -104, 58, 3, 101, -4, -18, -72, -74,
                  -68, -68, -104, -98, -120, -59, 98, 80, 95, -33, 0, 29, -99, -99, -54, -97, -73, 110, 105, 115, 115, 15, -64, -23, 116, -90, -41, -67, -34, 26,
                  -42, 83, -54, 36, 110, 127, -109, 30, 31, -12, 88, 96, 62, 112, 15, 28, -10, 121, 112, 86, -20, -69, -10, -17, -31, 119, -113, 7, 2, 1,
                  57, 25, 84, 6, 96, 95, -27, 126, 88, 89, 91, -128, -43, -75, 70, -128, -22, -61, 59, -118, -57, -42, 126, -102, 20, 3, 75, 125, -19, 1,
                  24, 27, 27, 99, 19, 47, 117, 119, 67, 93, -78, 111, 110, 110, 82, 112, -116, 65, -121, -45, -59, 118, -48, 47, 55, 110, -80, 107, -114, -74,
                  -75, -79, -98, 82, 38, 20, 60, 4, -109, -61, 31, -79, -15, 106, -14, 88, 80, -5, -100, 27, -68, -51, 93, 81, -53, -29, -115, 95, -97, -73,
                  79, -68, 94, 117, -92, -21, -95, 116, 12, -98, 117, -63, -127, -50, 30, -80, -37, 108, -32, 12, 93, 3, -65, 63, 0, -95, -87, 113, 88, 14,
                  46, -79, 107, 90, 122, 63, -124, 90, 98, -75, -93, -70, -67, 62, -27, -44, -55, 30, 54, 17, -100, 11, -92, 3, -87, -122, 99, -100, -57, -122,
                  -41, -31, -11, -44, 50, 71, 78, -12, 41, -35, -25, 47, -78, -13, 49, 30, 11, -36, 21, 53, -80, -71, 30, 15, 111, 42, -101, 65, 45, 58,
                  -27, -88, 78, -8, -91, 99, -128, -33, 8, -40, 44, 106, 44, -99, -61, -7, -70, -74, 70, -64, -21, -16, 122, 106, 12, -40, 61, 67, 42, 56,
                  61, 61, 11, -85, -79, -40, -114, -77, 20, -114, 113, 62, 21, 72, -51, 83, -53, -8, 90, 94, 84, 90, -33, -4, 12, 18, -49, 88, 33, -78,
                  -74, 4, -119, -123, -96, 101, 45, 26, 86, -30, 15, -93, 106, 117, 125, -89, 116, 12, -16, 104, -108, -104, -8, 29, 18, 115, 99, 16, 91, -80,
                  -79, 117, -100, 111, 122, -19, 19, 118, 29, 69, 6, 59, 110, -96, -15, -79, -109, 35, 121, -114, -62, 11, -121, 6, -121, 52, -20, 113, -116, -13,
                  -69, 53, 106, -103, 71, -31, 56, -124, -93, -113, -110, 103, -28, 72, -61, -4, 127, -42, 1, 107, 107, 127, -56, -31, 114, 75, -56, 96, -99, 109,
                  8, -65, 63, -62, 122, 28, -37, 54, -42, 73, 51, -80, -92, 126, -104, 73, -98, 9, -15, -79, -45, -107, 111, 47, -61, -46, 82, 24, -38, -37,
                  -113, -62, -16, -16, 37, -16, -7, 60, 108, 30, -41, 59, -68, -66, 29, 97, 106, -103, -64, -51, -17, -39, -103, -40, 11, 62, -120, -32, -79, -32,
                  -123, 86, 24, -7, -18, 58, 120, 60, 110, 41, 25, -124, -109, 99, 87, -37, 25, 88, 24, 29, -38, -38, 36, -37, -21, -55, 111, 6, -110, 12,
                  -46, -33, 12, 120, 119, -115, 59, 5, -17, -80, -15, -15, 19, -34, 100, 96, -17, -36, -34, 65, -72, -2, 100, -93, -106, -7, -19, -26, 79, -84,
                  119, 52, -9, 66, 87, -1, 15, 74, -53, -15, 115, 74, -33, -37, 125, 114, 51, 120, -11, -100, 114, -20, -3, 81, 5, -57, -72, 73, 82, -21,
                  36, 25, 100, -66, -90, 14, 5, -125, 57, 95, 85, -29, 124, -82, -41, -40, -108, 50, -117, 19, 99, 26, -86, 7, -40, -105, 25, -28, 102, -80,
                  -37, 58, 21, 6, -122, 58, 41, -44, 60, -106, 50, 3, 126, 12, 68, -32, -90, 26, -27, -92, 80, 115, 121, -118, 97, 64, -59, -27, -31, -59,
                  64, -108, -38, 81, -115, 114, 82, -88, -71, 60, -59, 48, -96, -30, -14, -16, 98, 32, 74, -19, -88, 70, 56, 41, -44, 92, 30, -3, 12, -24,
                  -72, 60, -68, 24, -120, 84, 59, -54, 31, 95, 126, -84, 21, -22, -92, 100, 54, 10, 46, -113, 30, 47, 39, 39, 3, 19, -69, 60, -68, 24,
                  -120, 92, 59, -106, -54, -102, -62, -99, 20, 108, -108, 92, 30, 61, 94, 78, 22, 3, -109, -69, 60, -68, 24, -120, 92, 59, -86, 30, 39, -123,
                  -102, -53, 99, 4, 3, -77, -69, 60, -68, 24, -120, 92, 59, -86, 94, 39, -123, -102, -53, 83, 44, 3, 10, 46, 15, 47, 6, -94, -42, -114,
                  90, -84, -109, 66, -51, -27, -47, -49, -128, -114, -53, -61, -117, -127, 104, -75, 99, 41, -58, 73, -95, -26, -14, 20, -53, -128, -126, -53, -61, -117,
                  -127, -120, -75, -93, 22, -29, -92, 80, 115, 121, 12, 97, 96, 114, -105, -121, 23, 3, 33, 107, 71, -81, -109, 66, -51, -27, 41, 5, 3, -77,
                  -71, 60, -68, 24, -120, 90, 59, 101, 39, -59, -92, 94, 14, 47, 110, 50, 49, 48, -36, 77, -94, -30, -14, -120, -52, -128, -105, -53, 99, 6,
                  6, 70, -42, -114, -31, 110, 18, 21, -105, 71, 100, 6, -68, 92, 30, 51, 48, 48, -78, 118, 12, 116, -109, -24, -72, 60, 34, 51, -32, -27,
                  -14, -120, -49, -64, -8, -38, 49, -50, 77, 50, -79, -53, -77, -89, 94, -114, 32, 46, -113, -23, 24, -108, -96, 118, -116, 113, -109, 76, -18, -14,
                  -20, -103, -105, 35, -112, -53, 99, 42, 6, 37, -86, 29, 11, -70, 34, 53, -121, 90, -75, -65, -66, 62, -97, -27, -105, 36, 50, -4, -110, 10,
                  79, -29, -114, -41, -22, -68, 50, -89, 78, -10, 104, 35, -105, -81, 48, -65, -28, -40, -53, -81, 100, -7, 37, -93, -41, 127, -42, 50, -3, 18,
                  61, 25, -9, -119, 62, 16, -103, 65, 102, 38, -19, -14, -88, -106, 45, -105, -89, -22, -96, -14, -49, -94, 34, 21, -125, 82, -43, -114, 33, 110,
                  18, 5, -105, 71, 100, 6, -68, 92, 30, -77, 48, 40, 85, -19, 88, -78, 92, 17, -10, -45, 2, -84, -8, -73, -26, -14, -14, 75, 56, 100,
                  50, -3, 18, 124, -99, -34, -37, -5, 6, 20, -30, -92, -28, -101, 17, -103, 65, 42, -77, -87, 68, 26, -30, -101, -98, -127, -90, -82, -2, -127,
                  66, 92, 30, 74, 12, 74, 81, 59, -22, -109, -82, 72, 74, -72, -38, 104, 60, 13, -95, -103, 40, 27, -57, -94, -113, -39, 122, -42, 115, 91,
                  78, -103, 76, -65, 4, -1, -104, -108, 95, -126, -29, -108, 95, 98, 68, 70, 100, 6, -103, -103, 72, 36, 14, 21, 53, 91, 46, -113, -84, 12,
                  74, 81, 59, -58, -70, 73, 38, 119, 121, 68, 102, -64, -53, -27, 49, 21, 3, -93, 107, 71, 36, 39, 101, 47, 93, 30, -111, 25, -16, 114,
                  121, -52, -52, -64, -120, -38, -111, -58, 73, 41, 103, -12, 101, 100, -6, -104, -50, 77, -30, -27, -14, 80, 101, 80, 72, 70, -74, 102, 58, 55,
                  -119, -105, -53, 67, -107, 65, 33, 25, -23, 55, 3, 53, 39, 69, -113, -53, 67, -109, 65, 97, 25, 25, -101, -48, 110, 18, 47, -105, -121, -102,
                  -105, -93, 39, -45, -7, -42, 5, 69, -10, -51, 32, -76, -101, -60, -53, -27, -95, -26, -27, -24, -51, 72, -65, 25, 68, 119, -109, 120, -72, 60,
                  20, -67, 28, 61, 25, -23, -113, 73, -20, -7, -22, -10, -71, -14, -2, -43, -9, -64, -29, -78, 50, 120, -50, -3, 9, -26, 125, -92, -2, -27,
                  71, 46, 104, -26, -56, 84, 64, -107, -35, -13, 64, -75, 59, 62, -75, 58, 124, 95, -40, 58, 46, -124, -122, 47, -115, 100, 121, 57, -76, 25,
                  -28, -97, -111, -6, -101, -127, -78, -109, -94, -57, -27, -95, -54, -96, -112, -116, -12, 79, -109, -88, 57, 41, 122, 92, 30, -54, 12, -14, -51, -108,
                  55, 3, -48, 115, 82, -12, -72, 60, -28, 25, -28, -111, -111, -70, 81, 116, 82, -12, -72, 60, -78, 49, 120, -38, -70, -116, -97, -1, 5, 24,
                  0, 122, 109, 26, -29, -40, 2, -55, -44, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66, 96, -126
               })
            );
            //If you want to save the image, you can use the following line:
            //ImageIO.write(baseImage, "PNG", new File("RatingBase.png"));

            BufferedImage[] result = new BufferedImage[11];
            int posY = 0;
            for (int i = 0; i < 11; ++i) {
                if (i > 0  && i % 3 == 0) {
                    posY += JR_HEIGHT;
                }
                result[i] = baseImage.getSubimage(i % 3 * JR_WIDTH, posY, JR_WIDTH, JR_HEIGHT);
                //If <ou want to save the single sprites, you can use the following line:
                //ImageIO.write(result[i], "PNG", new File(i + ".png"));
            }
            return result;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    private boolean ratingFixed = false;
    private double ratingValue;
    private JLabel ratingLabel;

    /**
     * Constructor
     */
    public JRating() {
        this(0, true, null);
    }

    /**
     * Constructor
     * @param ratingLabel - Instance of a JLabel-Object to show the Rating-Value as a number.
     */
    public JRating(JLabel ratingLabel) {
        this(0, true, ratingLabel);
    }

    /**
     * Constructor
     * @param ratingFixed - Claims, if the value can be changed.
     */
    public JRating(boolean ratingFixed) {
        this(0, ratingFixed, null);
    }

    /**
     * Constructor
     * @param ratingFixed - Claims, if the value can be changed.
     * @param ratingLabel - Instance of a JLabel-Object to show the Rating-Value as a number.
     */
    public JRating(boolean ratingFixed, JLabel ratingLabel) {
        this(0, ratingFixed, ratingLabel);
    }

    /**
     * Constructor
     * @param rating - Value of the shown rating.
     */
    public JRating(double rating) {
        this(rating, true, null);
    }

    /**
     * Constructor
     * @param rating - Value of the shown rating.
     * @param ratingLabel - Instance of a JLabel-Object to show the Rating-Value as a number.
     */
    public JRating(double rating, JLabel ratingLabel) {
        this(rating, true, ratingLabel);
    }

    /**
     * Constructor
     * @param rating - Value of the shown rating.
     * @param ratingFixed - Claims, if the value can be changed.
     */
    public JRating(double rating, boolean ratingFixed) {
        this(rating, ratingFixed, null);
    }

    /**
     * Constructor  
     * @param rating - Value of the shown rating.
     * @param ratingFixed - Claims, if the value can be changed.
     * @param ratingLabel - Instance of a JLabel-Object to show the Rating-Value as a number.
     */
    public JRating(double rating, boolean ratingFixed, JLabel ratingLabel) {
        this.ratingFixed = ratingFixed;
        this.ratingLabel = ratingLabel;
        ratingValue = getCorrectedRating(rating);
        setSize(JR_DIMENSION);
        //setDoubleBuffered(true);
        addMouseMotionListener(this);
        repaint();
    }

    /**
     * Returns, if changes are allowd.
     * @return boolean
     */
    public boolean isFixed() {
        return ratingFixed;
    }

    /**
     * Set the permission to change the rating.
     * @param ratingFixed boolean
     */
    public void setFixed(boolean ratingFixed) {
        this.ratingFixed = ratingFixed;
    }

    /**
     * Returns the current rating.
     * @return double
     */
    public double getRating() {
        return ratingValue;
    }

    /**
     * Set the rating-value.
     * @param ratingValue double
     */
    public void setRating(double ratingValue) {
        this.ratingValue = getCorrectedRating(ratingValue);
    }

    /**
     * Returns the JLabel-Object to the JRating-Object. (null if no JLabel is set.)
     * @return JLabel
     */
    public JLabel getRatingLabel() {
        return ratingLabel;
    }

    /**
     * Set the JLabel-Object.
     * @param ratingLabel JLabel
     */
    public void setRatingLabel(JLabel ratingLabel) {
        this.ratingLabel = ratingLabel;
    }

    /*
        Überschriebene Funktion...
     */
    public void mouseDragged(MouseEvent e) {
        if (ratingFixed) {
            return;
        }
        int mouseX = (int)e.getPoint().getX();
        int mouseY = (int)e.getPoint().getY();
        if (mouseX >= -1 && mouseX <= JR_WIDTH && mouseY >= 0 && mouseY <= JR_HEIGHT) {
            double newRatingValue = mouseX / 6 * .5;
            if (newRatingValue != ratingValue) {
                ratingValue = newRatingValue;
                repaint();
            }
        }
    }

    /*
        Implemented function...
     */
    public void mouseMoved(MouseEvent e) {}

    /*
        Overwritten function...
     */
    public void paintComponent(Graphics g) {
        g.drawImage(RATING_SPRITES[(int)(ratingValue * 2)], 0, 0, this);
        g.dispose();
        if (ratingLabel != null) {
            ratingLabel.setText(String.valueOf(ratingValue));
        }
    }

    /*
        Fix the rating-value to a valid value.
     */
    private double getCorrectedRating(double rating) {
        if (rating < 0) {
            return 0;
        } else if (rating > 5) {
            return 5;
        } else if (rating % 1 > 0) {
            double ratingDigits = rating % 1;
            return (int)rating + (ratingDigits == .0 ? .0 : ratingDigits == .5 ? .5 : Math.round(ratingDigits));
        } else {
            return rating;
        }
    }
    
    /*
        Overwritten function...
     */
    @Override
    public void setPreferredSize(Dimension preferredSize) {
        super.setPreferredSize(JR_DIMENSION);
    }

    /*
        Overwritten function...
     */
    @Override
    public void setSize(Dimension d) {
        super.setSize(JR_DIMENSION);
    }

    /*
        Overwritten function...
     */
    @Override
    public void setSize(int width, int height) {
        super.setSize(JR_WIDTH, JR_HEIGHT);
    }

    /*
        Overwritten function...
     */
    @Override
    public void setMaximumSize(Dimension maximumSize) {
        super.setMaximumSize(JR_DIMENSION);
    }

    /*
        Overwritten function...
     */
    @Override
    public void setMinimumSize(Dimension minimumSize) {
        super.setMinimumSize(JR_DIMENSION);
    }

}
